package org.gdstash.db;

import org.gdstash.description.BonusDetail;
import org.gdstash.description.BonusInfo;
import org.gdstash.file.ARCDecompress;
import org.gdstash.file.ARZRecord;
import org.gdstash.file.GDWriter;
import org.gdstash.ui.GDStashFrame;
import org.gdstash.util.GDConstants;
import org.gdstash.util.GDMsgFormatter;
import org.gdstash.util.GDMsgLogger;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class DBConstellation implements Comparable<DBConstellation> {
    private static final String TABLE_NAME = "GDC_CONSTELLATION";
    private static final int ROW_CONSTELLATION_ID = 1;
    private static final int ROW_NAME = 2;
    private static final int ROW_DESCRIPTION = 3;
    private String constellationID;
    private String name;
    private String description;
    private List<DBConstellationAffinity> dbAffinities;
    private List<DBConstellationStar> dbStars;

    public DBConstellation() {
        this.dbAffinities = new LinkedList<DBConstellationAffinity>();
        this.dbStars = new LinkedList<DBConstellationStar>();
    }

    public DBConstellation(final ARZRecord record) {
        this();
        this.constellationID = record.getFileName();
        this.setName(record.getConstellationNameTag());
        this.setDescription(record.getConstellationInfoTag());
        this.dbAffinities = record.getConstellationAffinityList();
        this.dbStars = record.getConstellationStarList();
    }

    private static List<DBConstellation> wrap(final ResultSet rs) throws SQLException {
        final LinkedList<DBConstellation> list = new LinkedList<DBConstellation>();
        while (rs.next()) {
            final DBConstellation constellation = new DBConstellation();
            constellation.constellationID = rs.getString(1);
            constellation.name = rs.getString(2);
            constellation.description = rs.getString(3);
            constellation.dbAffinities = DBConstellationAffinity.getByConstellationID(constellation.constellationID);
            constellation.dbStars = DBConstellationStar.getByConstellationID(constellation.constellationID);
            if (constellation.name != null) {
                final ByteBuffer buffer = StandardCharsets.UTF_8.encode(constellation.name);
                final String utf8 = StandardCharsets.UTF_8.decode(buffer).toString();
                constellation.name = utf8;
            }
            if (constellation.description != null) {
                final ByteBuffer buffer = StandardCharsets.UTF_8.encode(constellation.description);
                final String utf8 = StandardCharsets.UTF_8.decode(buffer).toString();
                constellation.description = utf8;
            }
            list.add(constellation);
        }
        return list;
    }

    @Override
    public boolean equals(final Object o) {
        if (o == null) {
            return false;
        }
        if (!o.getClass().equals(DBConstellation.class)) {
            return false;
        }
        final DBConstellation constellation = (DBConstellation) o;
        return constellation.constellationID.equals(this.constellationID);
    }

    @Override
    public int hashCode() {
        return this.constellationID.hashCode();
    }

    @Override
    public int compareTo(final DBConstellation o) {
        if (!o.getClass().equals(DBConstellation.class)) {
            final Object[] args = {DBConstellation.class, o.toString()};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_NOT_OF_TYPE", args);
            throw new ClassCastException(msg);
        }
        final DBConstellation constellation = o;
        return this.constellationID.compareTo(constellation.constellationID);
    }

    public String getConstellationID() {
        return this.constellationID;
    }

    public String getName() {
        return this.name;
    }

    private void setName(final String name) {
        this.name = GDStashFrame.arcList.getTag(ARCDecompress.FileModule.All, GDConstants.TAG_TEXT_SKILLS, name, false);
    }

    public String getDescription() {
        return this.description;
    }

    private void setDescription(final String description) {
        this.description = GDStashFrame.arcList.getTag(ARCDecompress.FileModule.All, GDConstants.TAG_TEXT_SKILLS, description, true);
    }

    public List<DBConstellationAffinity> getAffinityList() {
        return this.dbAffinities;
    }

    public static void createTables() throws SQLException {
        final String dropTable = "DROP TABLE GDC_CONSTELLATION";
        final String createTable = "CREATE TABLE GDC_CONSTELLATION (CONSTELLATION_ID VARCHAR(256) NOT NULL, NAME             VARCHAR(256), DESCRIPTION      VARCHAR(1024), PRIMARY KEY (CONSTELLATION_ID))";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final Statement st = conn.createStatement()) {
                if (GDDBUtil.tableExists(conn, "GDC_CONSTELLATION")) {
                    st.execute(dropTable);
                }
                st.execute(createTable);
                st.close();
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {"GDC_CONSTELLATION"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_CREATE_TABLE", args);
                GDMsgLogger.addError(msg);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        }
        DBConstellationAffinity.createTable();
        DBConstellationStar.createTable();
    }

    public static void delete(final String constellationID) throws SQLException {
        final String deleteEntry = "DELETE FROM GDC_CONSTELLATION WHERE CONSTELLATION_ID = ?";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(deleteEntry)) {
                ps.setString(1, constellationID);
                ps.executeUpdate();
                ps.close();
                DBConstellationAffinity.delete(conn, constellationID);
                DBConstellationStar.delete(conn, constellationID);
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {constellationID, "GDC_CONSTELLATION"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_DEL_TABLE_BY_ID", args);
                GDMsgLogger.addError(msg);
                GDMsgLogger.addError(ex);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        } catch (SQLException ex2) {
            throw ex2;
        }
    }

    public static void insert(final ARZRecord record) throws SQLException {
        final DBConstellation entry = get(record.getFileName());
        if (entry != null) {
            return;
        }
        final DBConstellation constellation = new DBConstellation(record);
        if (constellation.getStarList() == null) {
            return;
        }
        if (constellation.getStarList().isEmpty()) {
            return;
        }
        final String insert = "INSERT INTO GDC_CONSTELLATION VALUES (?,?,?)";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(insert)) {
                ps.setString(1, constellation.constellationID);
                ps.setString(2, constellation.name);
                ps.setString(3, constellation.description);
                ps.executeUpdate();
                ps.close();
                conn.commit();
                DBConstellationAffinity.insert(conn, constellation);
                DBConstellationStar.insert(conn, constellation);
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {constellation.constellationID, "GDC_CONSTELLATION"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_INS_TABLE_BY_ID", args);
                GDMsgLogger.addLowError(msg);
                GDMsgLogger.addLowError(ex);
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static void insertMod(final ARZRecord record) throws SQLException {
        final DBItemSet constellation = DBItemSet.get(record.getFileName());
        if (constellation != null) {
            delete(record.getFileName());
        }
        insert(record);
    }

    public static DBConstellation get(final String constellationID) {
        DBConstellation constellation = null;
        final String command = "SELECT * FROM GDC_CONSTELLATION WHERE CONSTELLATION_ID = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, constellationID);
            try (final ResultSet rs = ps.executeQuery()) {
                final List<DBConstellation> list = wrap(rs);
                if (list.isEmpty()) {
                    constellation = null;
                } else {
                    constellation = list.get(0);
                }
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            final Object[] args = {constellationID, "GDC_CONSTELLATION"};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex2);
        }
        return constellation;
    }

    public static List<DBConstellation> getAll() {
        List<DBConstellation> list = null;
        final String command = "SELECT * FROM GDC_CONSTELLATION";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            try (final ResultSet rs = ps.executeQuery()) {
                list = wrap(rs);
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            final Object[] args = {"<all>", "GDC_CONSTELLATION"};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex2);
        }
        return list;
    }

    public List<DBConstellationStar> getStarList() {
        return this.dbStars;
    }

    public static void createFile(final String filename) {
        final List<DBConstellation> list = getAll();
        String data = "";
        data += getHeaderLine();
        Collections.<DBConstellation>sort(list);
        for (final DBConstellation constellation : list) {
            Collections.<DBConstellationAffinity>sort(constellation.dbAffinities);
            Collections.<DBConstellationStar>sort(constellation.dbStars);
            data += getConstellationSummary(constellation);
            for (final DBConstellationStar star : constellation.dbStars) {
                data += getStarSummary(constellation, star);
            }
        }
        try {
            GDWriter.write(filename, data);
        } catch (Exception ex) {
            GDMsgLogger.addError(ex);
        }
    }

    private static String getHeaderLine() {
        final String s = "Constellation\tAscendant\tChaos\tEdlritch\tOrder\tPrimordial\tStar\tBonus Value\tBonus Type\tPet Bonus\t# Stars\tAscendant\tChaos\tEdlritch\tOrder\tPrimordial" + GDConstants.LINE_SEPARATOR;
        return s;
    }

    private static String getConstellationSummary(final DBConstellation constellation) {
        String s = constellation.name;
        int tabCount = 0;
        for (final DBConstellationAffinity affinity : constellation.dbAffinities) {
            if (!affinity.isRequired()) {
                continue;
            }
            if (affinity.getAffinity().equals("Ascendant")) {
                while (tabCount < 1) {
                    s += '\t';
                    ++tabCount;
                }
                s += Integer.toString(affinity.getPoints());
            }
            if (affinity.getAffinity().equals("Chaos")) {
                while (tabCount < 2) {
                    s += '\t';
                    ++tabCount;
                }
                s += Integer.toString(affinity.getPoints());
            }
            if (affinity.getAffinity().equals("Eldritch")) {
                while (tabCount < 3) {
                    s += '\t';
                    ++tabCount;
                }
                s += Integer.toString(affinity.getPoints());
            }
            if (affinity.getAffinity().equals("Order")) {
                while (tabCount < 4) {
                    s += '\t';
                    ++tabCount;
                }
                s += Integer.toString(affinity.getPoints());
            }
            if (!affinity.getAffinity().equals("Primordial")) {
                continue;
            }
            while (tabCount < 5) {
                s += '\t';
                ++tabCount;
            }
            s += Integer.toString(affinity.getPoints());
        }
        while (tabCount < 6) {
            s += '\t';
            ++tabCount;
        }
        s += "0";
        while (tabCount < 10) {
            s += '\t';
            ++tabCount;
        }
        s += Integer.toString(constellation.dbStars.size());
        for (final DBConstellationAffinity affinity : constellation.dbAffinities) {
            if (affinity.isRequired()) {
                continue;
            }
            if (affinity.getAffinity().equals("Ascendant")) {
                while (tabCount < 11) {
                    s += '\t';
                    ++tabCount;
                }
                s += Integer.toString(affinity.getPoints());
            }
            if (affinity.getAffinity().equals("Chaos")) {
                while (tabCount < 12) {
                    s += '\t';
                    ++tabCount;
                }
                s += Integer.toString(affinity.getPoints());
            }
            if (affinity.getAffinity().equals("Eldritch")) {
                while (tabCount < 13) {
                    s += '\t';
                    ++tabCount;
                }
                s += Integer.toString(affinity.getPoints());
            }
            if (affinity.getAffinity().equals("Order")) {
                while (tabCount < 14) {
                    s += '\t';
                    ++tabCount;
                }
                s += Integer.toString(affinity.getPoints());
            }
            if (!affinity.getAffinity().equals("Primordial")) {
                continue;
            }
            while (tabCount < 15) {
                s += '\t';
                ++tabCount;
            }
            s += Integer.toString(affinity.getPoints());
        }
        s += GDConstants.LINE_SEPARATOR;
        return s;
    }

    private static String getStarSummary(final DBConstellation constellation, final DBConstellationStar star) {
        String pre = constellation.name;
        int tabCount;
        for (tabCount = 0; tabCount < 6; ++tabCount) {
            pre += '\t';
        }
        pre = pre + Integer.toString(star.getIndex()) + '\t';
        String all = "";
        final DBSkill skill = star.getSkill();
        if (skill.isDevotion()) {
            String s = pre + skill.getName() + '\t' + "Skill";
            for (int tc2 = tabCount; tc2 < 15; ++tc2) {
                s += '\t';
            }
            all = all + s + GDConstants.LINE_SEPARATOR;
        }
        if (skill.getSkillID().equals("records/skills/devotion/tier1_27c.dbr")) {
            System.out.println("lala);");
        }
        List<DBStat> stats = DBStat.getByLevel(skill.getStatListNoPet(), 1);
        List<BonusInfo> infos = BonusDetail.createStatInfos(stats, null);
        for (final BonusInfo info : infos) {
            String s2 = pre;
            s2 = s2 + info.text + '\t';
            s2 = s2 + info.statDesc + '\t';
            for (int tc3 = tabCount; tc3 < 15; ++tc3) {
                s2 += '\t';
            }
            all = all + s2 + GDConstants.LINE_SEPARATOR;
        }
        if (skill.getPetSkill() != null) {
            stats = DBStat.getByLevel(skill.getPetSkill().getStatList(), 1);
            infos = BonusDetail.createStatInfos(stats, null);
            for (final BonusInfo info : infos) {
                String s2 = pre;
                s2 = s2 + info.text + '\t';
                s2 = s2 + info.statDesc + '\t';
                s2 = s2 + "Y" + '\t';
                for (int tc3 = tabCount; tc3 < 15; ++tc3) {
                    s2 += '\t';
                }
                all = all + s2 + GDConstants.LINE_SEPARATOR;
            }
        }
        return all;
    }
}
